﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using HigLabo.Core;

namespace HigLabo.Component
{
    /// <summary>
    /// 指定した処理を別スレッドで繰り返し実行する機能を提供するクラスです。
    /// </summary>
    public sealed class RepeatActionService
    {
        /// <summary>
        /// アクションの開始時に一度だけ発生するイベントです。
        /// </summary>
        public event EventHandler<EventArgs> Started;
        /// <summary>
        /// 毎回のアクションの実行直前に発生するイベントです。
        /// </summary>
        public event EventHandler<EventArgs> Executing;
        /// <summary>
        /// 毎回のアクションの実行直後に発生するイベントです。
        /// </summary>
        public event EventHandler<EventArgs> Executed;
        /// <summary>
        /// エラーが発生したときに発生するイベントです。
        /// </summary>
        public event EventHandler<ExceptionEventArgs> Error;
        /// <summary>
        /// この処理が終了するときに一度だけ発生するイベントです。
        /// </summary>
        public event EventHandler<EventArgs> Ended;
        private Action _Action = null;
        private Thread _Thread = null;
        private Boolean _IsStop = false;
        private AutoResetEvent _AutoResetEvent = new AutoResetEvent(false);
        private Int32 _IntervalMilliSeconds = 60 * 1000;
        private Int32? _ErrorRetryCount = 10;
        /// <summary>
        /// 
        /// </summary>
        public Int32 IntervalMilliSeconds
        {
            get { return _IntervalMilliSeconds; }
            set { _IntervalMilliSeconds = value; }
        }
        /// <summary>
        /// 
        /// </summary>
        public Thread Thread
        {
            get { return _Thread; }
        }
        /// <summary>
        /// 
        /// </summary>
        public Int32? ErrorRetryCount
        {
            get { return _ErrorRetryCount; }
            set { _ErrorRetryCount = value; }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="action"></param>
        public RepeatActionService(Action action)
        {
            _Action = action;
            _Thread = new Thread(this.Execute);
            _Thread.IsBackground = true;
            _Thread.Priority = ThreadPriority.BelowNormal;
        }
        /// <summary>
        /// 
        /// </summary>
        public void Start()
        {
            _Thread.Start();
        }
        /// <summary>
        /// 
        /// </summary>
        public void Stop()
        {
            _IsStop = true;
            _AutoResetEvent.Set();
        }
        private void Execute(Object state)
        {
            Int32 errorCount = 0;
            Exception exception = null;

            this.OnStarted();

            while (true)
            {
                exception = null;

                try
                {
                    this.OnExecuting();
                    this._Action();
                    this.OnExecuted();
                    errorCount = 0;
                }
                catch (Exception ex)
                {
                    errorCount += 1;
                    exception = ex;
                }
                if (exception != null)
                {
                    try
                    {
                        this.OnError(exception);
                    }
                    catch { }
                }
                //指定した回数エラーが発生したら処理を終了する
                if (this.ErrorRetryCount.HasValue == true &&
                    errorCount == this.ErrorRetryCount)
                { break; }
                _AutoResetEvent.WaitOne(this.IntervalMilliSeconds);
                if (_IsStop == true) { break; }
            }
            this.OnEnded();
        }
        private void OnStarted()
        {
            var eh = this.Started;
            if (eh != null)
            {
                eh(this, EventArgs.Empty);
            }
        }
        private void OnExecuting()
        {
            var eh = this.Executing;
            if (eh != null)
            {
                eh(this, EventArgs.Empty);
            }
        }
        private void OnExecuted()
        {
            var eh = this.Executed;
            if (eh != null)
            {
                eh(this, EventArgs.Empty);
            }
        }
        private void OnError(Exception ex)
        {
            var eh = this.Error;
            if (eh != null)
            {
                eh(this, new ExceptionEventArgs(ex));
            }
        }
        private void OnEnded()
        {
            var eh = this.Ended;
            if (eh != null)
            {
                eh(this, EventArgs.Empty);
            }
        }
    }
}
